package com.ltu.timkor6.gamesite.games.towerofhanoi.client;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.ltu.timkor6.gamesite.shared.CompositeAbstract;
import com.ltu.timkor6.gamesite.shared.Result;

/**
 * Tower of Hanoi
 * 
 * @author tim
 */


public class TowerOfHanoiComposite extends CompositeAbstract
{
	private static final String CSS_HANOI_TABLE = "hanoi-table";
	private static final String CSS_HANOI_PANEL = "hanoi-panel";
	private static final String CSS_HANOI_CELL = "hanoi-cell";
	private static final String CSS_HANOI_SPACER = "hanoi-spacer";
	private static final String CSS_HANOI_LABEL = "hanoi-label";
	private static final String CSS_HANOI_LABEL_ACTIVE = "hanoi-label-active";
	
	private static final int COLUMNS = 3;
	private static final int FIRSTCOLUMN = 0;
	
	private final int CELL_WIDTH = 150;
	
	private final int ELEMENT_STANDARD_WIDTH = 100;
	private final int ELEMENT_INCREASE_WIDTH = 5;
	
	private TowerOfHanoi towerOfHanoi;
	private SimplePanel info;
	private SimplePanel content;
	private VerticalPanel infoPanel = new VerticalPanel();
	private FlexTable game;
	
	private TextBox number;
	private Cell cellToMove = null;
	private int elements;
	private int currentMoves;
	
	private Label labelMinimumMoves = new Label();
	private Label labelCurrentMoves;
	
	public TowerOfHanoiComposite(TowerOfHanoi towerOfHanoi)
	{
		this.towerOfHanoi = towerOfHanoi;
		
		// Title
		final Label title = new Label("Towers Of Hanoi");
		title.setStyleName("panelHeader");
		panel.add(title);
		
		info = new SimplePanel();
		info.setStyleName("game-info");
		content = new SimplePanel();
		content.setStyleName("game-playarea");
		
		panel.add(info);
		panel.add(content);
		
		loadModule();
		
		initWidget(panel);
	}
	
	public void loadModule()
	{
		this.info.setStyleName("errors");
		VerticalPanel startPanel = new VerticalPanel();
		
		Label label = new Label("Number of elements");
		
		number = new TextBox();
		number.setText("3");
		number.addKeyPressHandler(new KeyPressHandler()
		{
			public void onKeyPress(KeyPressEvent event)
			{
				if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER)
				{
					towerOfHanoi.checkSubmit();
				}
			}
		});
		
		Button submit = new Button("Start!");
		submit.addClickHandler(new ClickHandler()
		{
			public void onClick(ClickEvent event) {
				towerOfHanoi.checkSubmit();
			}
		});
		
		startPanel.add(label);
		startPanel.add(number);
		startPanel.add(submit);
		
		content.setWidget(startPanel);
	}
	
	public String getNumber()
	{
		return this.number.getText();
	}
	
	public void addError(String error)
	{
		this.info.setWidget(new Label(error));
	}

	public void setElements(int elements)
	{
		this.elements = elements;
	}
	
	public void startGame()
	{
		this.info.setWidget(infoPanel);
		this.info.setStyleName("game-info");
		
		infoPanel.add(labelMinimumMoves);
		
		labelCurrentMoves = new Label("Current moves: 0");
		infoPanel.add(labelCurrentMoves);
		
		// Set the outer bounds
		game = new FlexTable();
		game.setBorderWidth(1);
		game.setCellPadding(0);
		game.setCellSpacing(0);
		game.setStyleName(CSS_HANOI_TABLE);
		
		// Set the width of the columns
		int columnWidth = this.CELL_WIDTH + (this.ELEMENT_INCREASE_WIDTH * elements);
		game.getColumnFormatter().setWidth(0, columnWidth + "px");
		game.getColumnFormatter().setWidth(1, columnWidth + "px");
		game.getColumnFormatter().setWidth(2, columnWidth + "px");
		
		FocusPanel focusPanel;
		
		// Top spacer for column 0
		game.getCellFormatter().addStyleName(0, 0, CSS_HANOI_SPACER);
		focusPanel = createPanel(0, 0, true);
		
		// Top spacer for column 1
		game.getCellFormatter().addStyleName(0, 1, CSS_HANOI_SPACER);
		focusPanel = createPanel(0, 1, true);
		
		// Top spacer for column 2
		game.getCellFormatter().addStyleName(0, 2, CSS_HANOI_SPACER);
		focusPanel = createPanel(0, 2, true);
		
		// Draw our playing field
		int element = 1;
		for(int i = 1; i < elements+1; i++)
		{
			for(int j = 0; j < COLUMNS; j++)
			{
				// Place a label in each row in the first column
				if(j == FIRSTCOLUMN)
				{
					// We need a panel without click-abilities
					focusPanel = createPanel(i, j, false);
					
					Cell cell = createCell(element, i, j);
					
					focusPanel.setWidget(cell);
					element++;
				}
				else
				{
					// We need a panel with click-abilities
					focusPanel = createPanel(i, j, true);
				}
			}
		}
		
		content.setWidget(game);
	}
	
	/**
	 * Create a panel.
	 * @param column
	 * @param row
	 * @return
	 */
	private FocusPanel createPanel(int row, int column, boolean clickable)
	{
		FocusPanel focusPanel = new FocusPanel();
		focusPanel.setStyleName(CSS_HANOI_PANEL);
		
		// Make all panels accept clicks
		focusPanel.addClickHandler(this.addClickPanel(column));
		
		// Add it to a cell
		game.setWidget(row, column, focusPanel);
		game.getCellFormatter().addStyleName(row, column, CSS_HANOI_CELL);
		
		return focusPanel;
	}
	
	/**
	 * Create a cell.
	 * @param element
	 * @param column
	 * @param row
	 * @return
	 */
	private Cell createCell(int element, int row, int column)
	{
		Cell cell = new Cell(element, row, column);
		cell.setStyleName(CSS_HANOI_LABEL);
		int cellWidth = this.ELEMENT_STANDARD_WIDTH + (this.ELEMENT_INCREASE_WIDTH * element);
		cell.setWidth(cellWidth + "px");
		if(element == 1)
		{
			cell.setAtTop(true);
		}
		
		// Make the cells clickable
		cell.addClickHandler(this.addClickCell(row, column));
		
		return cell;
	}
	
	private void setCellToMove(Cell source)
	{
		source.setStyleName(CSS_HANOI_LABEL_ACTIVE);
		this.cellToMove = source;
	}

	private void checkColumn(FocusPanel focusPanel, int column)
	{
		if(focusPanel.getWidget() == null)
		{
			// If clicked on a panel without a cell in it
			if(this.cellToMove != null)
			{
				FocusPanel newPanel;	// Panel that is traversed
				Cell newCell = null;	// Cell that is traversed
				Cell prevCell = null;	// Previous cell to be traversed
				
				// Find row in column that is empty; check that the previous weight is heavier.
				// We start at the bottom and go down except for spacer cell.
				for(int row = this.elements; 0 < row; row--)
				{
					newPanel = (FocusPanel) game.getWidget(row, column);
					newCell = (Cell) newPanel.getWidget();
					
					// If cell is empty
					if(newCell == null && row == this.elements)
					{
						moveCell(newPanel, row, column, prevCell);
						return;
					}
					// If cell is smaler than the one on top
					else if(newCell == null && prevCell.isAtTop() && this.cellToMove.getSize() < prevCell.getSize())
					{
						moveCell(newPanel, row, column, prevCell);
						return;
					}
					else if(newCell != null)
					{
						prevCell = newCell;
					}
					else
					{
						break;
					}
				}
			}
		}
	}
	
	private void moveCell(FocusPanel toPanel, int row, int column, Cell prevCell)
	{
		// Remove cell from old panel
		this.cellToMove.removeFromParent();
		
		// Set new top at old column
		if(this.cellToMove.getRow() < elements)
		{
			FocusPanel topPanel = (FocusPanel) game.getWidget(this.cellToMove.getRow()+1, this.cellToMove.getColumn());
			Cell topCell = (Cell) topPanel.getWidget();
			topCell.setAtTop(true);
		}
		
		// Add cell to new panel
		toPanel.add(this.cellToMove);
		this.cellToMove.setRow(row);
		this.cellToMove.setColumn(column);
		
		// Old cell is not at top anymore
		if(prevCell != null)
		{
			prevCell.setAtTop(false);
		}
		
		this.updateMoves();
		
		this.checkIfWon();
		
		// Clear which cell to move
		this.cellToMove.setStyleName(CSS_HANOI_LABEL);
		this.cellToMove = null;
	}
	
	private void checkIfWon()
	{
		int column = 2;
		int count = 0;
		
		FocusPanel checkPanel;
		Cell checkCell;
		
		for(int row = this.elements; 0 < row; row--)
		{
			checkPanel = (FocusPanel) game.getWidget(row, column);
			checkCell = (Cell) checkPanel.getWidget();
			
			if(checkCell != null)
			{
				if(checkCell.getSize() != row)
				{
					break;
				}
				else if(checkCell.getSize() == row)
				{
					count++;
				}
			}
		}
		
		// If finished, send result to server
		if(count == this.elements)
		{
			this.cellToMove.setAtTop(false);
			this.towerOfHanoi.finished(this.currentMoves);
		}
	}
	
	public void finished(Result result)
	{
		if(result == null)
		{
			Window.alert("You failed");
		}
		else
		{
			Window.alert("Finished on " + this.currentMoves + " moves with " + result.getPoints() + " points.");
		}
	}
	
	private void updateMoves()
	{
		this.currentMoves++;
		this.labelCurrentMoves.setText("Current moves: " + currentMoves);
	}
	
	private ClickHandler addClickPanel(final int column)
	{
		ClickHandler handler =  new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				checkColumn((FocusPanel) event.getSource(), column);
			}
		};
		return handler;
	}
	
	private ClickHandler addClickCell(final int row, final int column)
	{
		ClickHandler handler = new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				Cell source = (Cell) event.getSource();
				
				if(source.isAtTop())
				{
					setCellToMove(source);
				}
			}
		};
		return handler;
	}

	public void setMinMoves(Integer minMoves)
	{
		this.labelMinimumMoves.setText("Minimum moves: " + minMoves);
	}
}
